home *** CD-ROM | disk | FTP | other *** search
- /*
- * File: mp_intp.c
- * SGoldthorpe 20-Jul-91
- */
-
- /*
- * mp_intp - the midi file interpreter for midiplay
- * This software is (C) 1991 Stephen Goldthorpe but it's FREE! Usual
- * disclaimers and notices about this software not being sold for profit.
- * But you may take all you want from the code though! If you have any
- * suggestions/bug fixes please get in contact with me. I don't want to
- * maintain code i've never even seen before (life's hard enough without all
- * of that)!
- * -Steve Goldthorpe
- * Phone (DAYTIME UK): +44 707 382350
- * Internet E-Mail: SGoldthorpe.wgc-e@rx.xerox.com
- * goldthor@arisia.xerox.com
- *
- * Version 0.5 by Piet van Oostrum <piet@cs.ruu.nl>
- * November 1991.
- * I made the following changes:
- * 1. Files > 32767 wouldn't play. I have changed a couple of ints to
- * LONG. Also malloc'ed the buffer with a variable rather than a fixed size.
- * 2. replaced array referenced with a[i] rather than *(a+i). I find this
- * more readable but it has the same meaning.
- * 3. Midifiles of more than about 3 minutes didn't play right. I think
- * this is a bug in ltod (long to double) in the floating point lib. I
- * changed the timing from using floating point arithmetic to LONG
- * arithmetic.
- * Anyway the timing was not robust in the presence of tempo changes
- * because the new time per beat would be applied to the time from the
- * beginning of the piece rather than from the time of the tempo change.
- * 4. I introduced the -T option to send timing commands. This can be
- * used to trigger a drum computer or an arranger.
- *
- * NOTE: I distribute this version with the consent of Steve
- * Goldthorpe. I think he should not be bothered with bugs in my version!
- */
-
- #include <stdio.h>
- #include <time.h>
- #include <string.h>
- #include "kernel.h"
- #include "bbc.h"
-
- /* and for the atari OS stuff */
- /* #include <osbind.h> */
-
-
-
- #include "mp_gbls.h"
-
- /* GLOBAL VARIABLES */
- /* The track_delta's are in units of 1/division clockticks */
- /* time_per_beat is the length of a quarter note in clock ticks */
-
- static long track_delta[MAX_TRACKS], division, time_per_beat;
- static BYTE *track_pos[MAX_TRACKS];
- static long track_left[MAX_TRACKS];
- static int track_finished[MAX_TRACKS];
- static clock_t clock_orig;
- static char *gFile;
- static WORD format, tracks;
- static int finished_tracks;
- static int trnr;
- static long clock_delta, clock_time;
-
- /* FUNCTION DECLS */
-
- BOOL interp(BYTE *buffer, char *file, LONG len);
- int sysex_event(BYTE event);
- int meta_event(void);
- static int system_common(BYTE event);
- static int system_real_time(void);
- static void all_notes_off(void);
- static void truncated(void);
-
-
- /* MACRO FUNCTIONS */
- /* the error checking may be a bit OTT but I'm gonna do it anyway (helps
- catch those naughty bugs - and bad files) */
- #define GET32BITS(dw,p,l) dw=(((LONG)(*p)<<24)+ \
- ((LONG)(*(p+1))<<16)+ \
- (((LONG)*(p+2))<<8)+ \
- (LONG)(*(p+3))); \
- if(l<4) \
- { truncated(); \
- return(FALSE); \
- } \
- p += 4; l -= 4
-
- #define GET16BITS(w,p,l) w=(((WORD)(*p)<<8)+(WORD)*(p+1)); \
- if(l<2) \
- { truncated(); \
- return(FALSE); \
- } \
- p += 2; l -= 2
-
- #define GET8BITS(b,p,l) b = *(p)++; \
- if(--l<0) \
- { truncated(); \
- return(FALSE); \
- }
-
- #define GETVARLEN(dw,p,l) for(dw=(LONG)(*p)&0x7f;(*(p)++)&0x80;) \
- { if(--l<0) \
- { truncated(); \
- return(FALSE); \
- } \
- dw <<=7; \
- dw |= (LONG)(*p)&0x7f; \
- } \
- if(--l<0) \
- { truncated(); \
- return(FALSE); \
- }
-
- #define CHECKLEFT(l,v) if(l<v) \
- { truncated(); \
- return(FALSE); \
- }
-
-
- #define MIDI_TXBYTE 0x404c9
-
-
- #define SEND(b) { \
- _kernel_swi_regs r; \
- r.r[0]=b; \
- while(_kernel_swi(MIDI_TXBYTE,&r,&r) != 0) { \
- r.r[0]=b; \
- } \
- } \
-
-
-
-
-
- /* FUNCTION DEFS */
- BOOL interp(BYTE *buffer, char *file, LONG len)
- { BYTE *pos=buffer,*next;
- BYTE running_status[MAX_TRACKS],last_running_status;
- WORD w;
- LONG dw,delta;
- LONG left=len;
-
- gFile=file;
-
- /* check header */
- if((left<4)||(strncmp("MThd",(char*)pos,4)!=0))
- { (void)fprintf(stderr,"%s: %s is not a midi file\n",app_name,file);
- return(FALSE);
- }
- pos += 4; left -= 4;
-
- /* find address of next chunk */
- GET32BITS(dw,pos,left);
- next=pos+dw;
- #ifdef DEBUG
- (void)printf("pos is %lx len is %ld next would be %lx\n",pos-buffer,dw,
- next-buffer);
- #endif
-
- /* get file format */
- GET16BITS(format,pos,left);
- switch (format)
- { /* OK we accept formats 0 and 1 */
- case 0:
- case 1:
- break;
- /* but we don't do any others */
- default:
- (void)fprintf(stderr,"%s: can't play %s, midi file type %d\n",app_name,
- file,format);
- return(FALSE);
- }
-
- /* get number of tracks */
- GET16BITS(tracks,pos,left);
-
- /* check tracks in range */
- if(tracks > MAX_TRACKS)
- { (void)fprintf(stderr,"%s: %s has too many tracks :%d (%d allowed).\n",
- app_name,file,tracks,MAX_TRACKS);
- return(FALSE);
- }
-
- /* get division - this is the division of a quarter note or if negative is
- frame based. */
- GET16BITS(w,pos,left);
- division=(LONG)w;
-
- /* I don't suport the frame stuff yet! */
- if(division < 0)
- { (void)fprintf(stderr,
- "%s: file %s - don't support framed based files yet!", app_name,
- file);
- return(FALSE);
- }
- #ifdef DEBUG
- (void)printf("division = %f\n",division);
- #endif
-
- time_per_beat = (LONG)(CLK_TCK/2); /* default 120bpm = 2bps */
- #ifdef DEBUG
- (void)printf("time per beat (1/%d sec) %ld\n", CLK_TCK, time_per_beat);
- #endif
-
- /* do some initialisation, track finding etc */
- finished_tracks = 0;
- for(trnr=0;trnr<tracks;trnr++)
- { CHECKLEFT(left,(next-pos));
- left -= next-pos; pos = next;
-
- /* track start */
- if((left<4)||(strncmp("MTrk",(char*)pos,4)!=0))
- { (void)fprintf(stderr,"%s: %s parse error, track expected\n",
- app_name,file);
- #ifdef DEBUG
- (void)printf("posn %lx, bytes around (-3..3) %02x %02x %02x %02x \
- %02x %02x %02x\n",pos-buffer,*(pos-3),*(pos-2),*(pos-1),*pos,*(pos+1),*(pos+2),
- *(pos+3));
- #endif
- return(FALSE);
- }
- pos += 4; left -= 4;
-
- /* find address of next chunk */
- GET32BITS(dw,pos,left);
- track_pos[trnr] = pos;
- next = pos+dw;
- track_left[trnr] = dw;
- #ifdef DEBUG
- (void)printf("pos is %lx len is %ld next would be %lx\n",pos-buffer,dw,
- next-buffer);
- #endif
-
- /* get initial delta time */
- GETVARLEN(dw,track_pos[trnr],track_left[trnr]);
- track_delta[trnr]=dw * time_per_beat;
- track_finished[trnr]=FALSE;
- running_status[trnr] = 0xfe;
- last_running_status = 0xfe;
- }
-
- /* let the user know what's happening */
- (void)printf("playing '%s' with %d %s\n",file,tracks,
- tracks==1?"track":"tracks");
-
- /* get start time */
- clock_orig=clock();
- clock_time=0;
- if (f_Timing) SEND (0xFA);
-
- /* dispatcher */
- while (finished_tracks != tracks)
- { BYTE event;
- clock_delta = (clock()-clock_orig)*division;
- if (f_Timing && clock_delta >= clock_time) {
- SEND(0xF8);
- clock_time += (time_per_beat * division / 24);
- }
- for(trnr=0;trnr<tracks;trnr++)
- { if (!track_finished[trnr] & (clock_delta >= track_delta[trnr]))
- { GET8BITS(event,track_pos[trnr],track_left[trnr]);
-
- /* parse event */
- switch (event)
- { /* meta-events */
- case 0xff:
- { if(!meta_event())
- return(FALSE);
- break;
- }
- /* sysex events */
- case 0xf0:
- case 0xf7:
- { if(!sysex_event(event))
- return(FALSE);
- break;
- }
- case 0xf1:
- case 0xf2:
- case 0xf3:
- case 0xf4:
- case 0xf5:
- case 0xf6:
- { if(!system_common(event))
- return(FALSE);
- break;
- }
- case 0xf8:
- case 0xf9:
- case 0xfa:
- case 0xfb:
- case 0xfc:
- case 0xfd:
- case 0xfe:
- { if(!system_real_time())
- return(FALSE);
- break;
- }
- /* midi events */
- default:
- { switch(event & 0xf0)
- { /* 3 byte events */
- case 0x90:
- case 0x80:
- case 0xa0:
- case 0xb0:
- case 0xe0:
- { BYTE c;
- SEND(event);
- GET8BITS(c,track_pos[trnr],track_left[trnr]);
- SEND(c);
- GET8BITS(c,track_pos[trnr],track_left[trnr]);
- SEND(c);
- running_status[trnr] = event;
- last_running_status = event;
- break;
- }
- /* program change */
- case 0xc0:
- { BYTE c;
- GET8BITS(c,track_pos[trnr],track_left[trnr]);
- if (f_Program)
- { SEND(event);
- SEND(c);
- running_status[trnr] = event;
- last_running_status = event;
- }
- break;
- }
- /* channel pressure */
- case 0xd0:
- { BYTE c;
- GET8BITS(c,track_pos[trnr],track_left[trnr]);
- if (f_Channel_pressure)
- { SEND(event);
- SEND(c);
- running_status[trnr] = event;
- last_running_status = event;
- }
- break;
- }
- default:
- { /* running status */
- if(running_status[trnr]!=last_running_status)
- { SEND(running_status[trnr]);
- last_running_status=running_status[trnr];
- }
- if((event&0x80)==0)
- { SEND(event);
- #ifdef DEBUG
- (void)printf("running stat (%02x) %02x\n",
- running_status[trnr],event);
- #endif
- switch(running_status[trnr] & 0xf0)
- { /* 3 byte events */
- case 0x90:
- case 0x80:
- case 0xa0:
- case 0xb0:
- case 0xe0:
- { BYTE c;
- GET8BITS(c,track_pos[trnr],
- track_left[trnr]);
- SEND(c);
- break;
- }
- /* ignoring other 0xf? events - naughty
- aren't I */
- }
- }
- }
- }
- }
- }
-
- /* get delta time - but only if we're still going on this
- track */
- if(!track_finished[trnr])
- { GETVARLEN(delta,track_pos[trnr],track_left[trnr]);
- track_delta[trnr] += delta * time_per_beat;
- }
- }
- }
- /* CHECK TO SEE IF CTRL C/S PRESSED */
- if(bbc_inkey(-2)) /* control pressed */
- {
- if (bbc_inkey(-83)) { /* CTRL C */
- printf("\n** Exit by CTRL C **\n");
- all_notes_off();
- return(TRUE);
- }
-
- if (bbc_inkey(-82)) { /* CTRL S */
- printf("\n** Skip track by CTRL S **\n");
- all_notes_off();
- return(FALSE);
-
- }
- }
- }
- if (f_Timing) SEND (0xFC);
-
- return(FALSE);
- }
-
-
- /* system exclusive events */
- int sysex_event(BYTE event)
- { LONG length,l;
- GETVARLEN(length,track_pos[trnr],track_left[trnr]);
- if(f_Sysex)
- { BYTE c;
- if(event==0xf0)
- SEND(0xf0);
- for(l=0;l<length;l++)
- { GET8BITS(c,track_pos[trnr],track_left[trnr]);
- SEND(c);
- }
- }
- else
- { CHECKLEFT(track_left[trnr],length);
- track_pos[trnr] += length;
- track_left[trnr] -= length;
- }
- return(TRUE);
- }
-
- /* meta-events */
- int meta_event(void)
- { BYTE type;
- LONG length;
-
- /* get type of meta event */
- GET8BITS(type,track_pos[trnr],track_left[trnr]);
- #ifdef DEBUG
- (void)printf("meta-event %02x ",type);
- #endif
-
- /* get length */
- GETVARLEN(length,track_pos[trnr],track_left[trnr]);
- #ifdef DEBUG
- (void)printf("length %ld\n",length);
- #endif
-
- switch(type)
- { /* textual meta-events */
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- { int flag;
- char *msg;
- switch(type)
- { case 0x01:
- flag=f_text;
- msg="Text: \"";
- break;
- case 0x02:
- flag=f_copyright;
- msg="Copyright: \"";
- break;
- case 0x03:
- flag=f_track_name;
- msg="Track\\Sequence: \"";
- break;
- case 0x04:
- flag=f_instrument;
- msg="Instrument: \"";
- break;
- case 0x05:
- flag=f_lyric;
- break;
- case 0x06:
- flag=f_marker;
- msg="Marker: \"";
- break;
- case 0x07:
- flag=f_prompt;
- msg="Prompt: \"";
- break;
- }
- if (flag)
- { LONG l;
- BYTE c;
- /* lyrics should have no header */
- if(type!=0x05)
- printf(msg);
- for(l=0;l<length;l++)
- { GET8BITS(c,track_pos[trnr],track_left[trnr]);
- putchar(c);
- }
- /* lyrics should be on same line */
- if(type!=0x05)
- printf("\"[Tr %d]\n",trnr);
- }
- else
- { CHECKLEFT(track_left[trnr],length);
- track_pos[trnr] += length;
- track_left[trnr] -= length;
- }
- break;
- }
- /* u-sec tempo set */
- case 0x51:
- { LONG t;
- BYTE c;
- int i;
- long delta;
- GET8BITS(c,track_pos[trnr],track_left[trnr]);
- t=(LONG)c<<16;
- GET8BITS(c,track_pos[trnr],track_left[trnr]);
- t += (LONG)c<<8;
- GET8BITS(c,track_pos[trnr],track_left[trnr]);
- t += (LONG)c;
- #ifdef DEBUG
- (void)printf("usec %ld ",t);
- #endif
- t=(t*(LONG)CLK_TCK)/1000000L;
-
- /* adjust all time values to the new unit system */
- /* Note: the calculations must be in long, not LONG as some of
- the intermediate values may become negative */
-
- /* printf("tpb= %ld t= %ld delta=%ld clock_time=%ld\n",
- time_per_beat, t, delta, clock_time);
- */
- delta = track_delta[trnr];
- clock_time = (clock_time-delta)*(long)t/time_per_beat + delta;
- for (i=0;i<tracks;i++) {
- /* printf("trnr= %d delta= %ld ", i, track_delta[i]); */
- if (!track_finished[i])
- track_delta[i] =
- (track_delta[i]-delta)*(long)t/time_per_beat + delta;
- /* printf("=> %ld\n", track_delta[i]); */
- }
- time_per_beat = t;
- #ifdef DEBUG
- (void)printf("our %ld\n",time_per_beat);
- #endif
- break;
- }
-
- /* end of track - can't be bothered to check left == 0 too */
- case 0x2f:
- track_finished[trnr]=TRUE;
- /* printf("track %d finished\n", trnr); */
- finished_tracks++;
- break;
-
- /* ignore rest */
- default:
- CHECKLEFT(track_left[trnr],length);
- track_pos[trnr] += length;
- track_left[trnr] -= length;
- break;
- }
- return(TRUE);
- }
-
- static int system_common(BYTE event)
- { switch(event)
- { /* 3 byte instructions */
- case 0xf2:
- { CHECKLEFT(track_left[trnr],2);
- track_pos[trnr] += 2;
- track_left[trnr] -= 2;
- break;
- }
- /* 2 byte instructions */
- case 0xf1:
- case 0xf3:
- { CHECKLEFT(track_left[trnr],1);
- track_pos[trnr] += 1;
- track_left[trnr] -= 1;
- break;
- }
- /* rest are single byte */
- }
- return(TRUE);
- }
-
- static int system_real_time(void)
- { /* all 1 byte so ignore */
- return(TRUE);
- }
-
- static void all_notes_off(void)
- {
- int i,j;
-
- /* MANUALLY TURN ALL NOTES OFF */
-
- printf("Turning all notes off...\n");
-
- if (f_Timing) SEND (0xFC);
-
- for(i=0;i<16;i++) {
- SEND((BYTE)(0x90+i));
- for(j=0;j<128;j++) {
- SEND(j);
- SEND(0);
- }
- }
- }
-
- /* general error messages - I got fed up typing these over and over again */
- static void truncated(void)
- { (void)fprintf(stderr,"%s: %s truncated\n",app_name,gFile);
- }
-
- /*
- * REVISION LOG
- * ============
- * 0.1 SGoldthorpe 20-Mar-91 Created for Atari ST / Sozobon C. It's
- * a bit atari specific in places but i've
- * tried to make it UNIX(tm) looking for
- * easier porting (if anyone feels brave
- * enough to try.
- * 0.2 SGoldthorpe 7-Apr-91 Messed up the code in mp_intp to
- * allow type 1 midi files. Timing is
- * still a bit hairy but it plays 80%
- * of the files I have OK.
- * 0.3 SGoldthorpe 27-May-91 Reformatted & tidied up, sorted out
- * running status and added flags.
- * 0.4 SGoldthorpe 20-Jul-91 Generally restructed and tidied up and
- * added sysex events.
- *
- */
-